package maze;

import java.util.HashSet;
import java.util.Set;

import com.googlecode.gunncs.mechanism.AbstractBot;
import com.googlecode.gunncs.mechanism.component.NodeFoundEvent;
import com.googlecode.gunncs.mechanism.component.NodeFoundListener;

/**
 * This encapsulates the controls and algorithms code --
 * <strong>Controls</strong> and <strong>Mechanisms</strong>.
 * <p>
 * Most of <strong>Control</strong>'s work is going to be here. The job of
 * <strong>Mechanics</strong> is to get the robot abstracted enough to be able
 * to use this controller (which deals exclusively with an idealized, abstract
 * robot).
 * </p>
 * 
 * Development stage: <em>May</em> be done.
 * 
 * @author Calvin
 */
public class Controller implements NodeFoundListener {
	/**
	 * The controller's representation of the robot.
	 * 
	 * @see AbstractBot
	 */
	private AbstractBot bot;

	/**
	 * The controller's representation of the maze.
	 */
	private Maze maze;

	public Controller(AbstractBot bot) {
		this.bot = bot;
		this.maze = new Maze(bot.openDirections());
	}

	/**
	 * This should be what is called by <strong>AbstractBot</strong>.
	 * 
	 * <p>
	 * This is where most of the <strong>Controls</strong> group's work is going
	 * to have to be focused.
	 * </p>
	 * <p>
	 * Once this is called by {@link AbstractBot}, it takes over control. It'll
	 * process data, perhaps make some queries back to the bot, then issue some
	 * commands and release control back to the bot.
	 * </p>
	 * 
	 * Precondition: The bot just carried out the previously-specified move.
	 */
	public void onNodeFound(NodeFoundEvent e) {
		// Enter the newly-found node into the maze (i.e., graph).
		Node newNode = new Node(maze.base.pathCost() + e.lastStep().magnitude,
				maze.base.heuristic(), bot.openDirections()); 
		maze.add(e.lastStep(), newNode);

		// Find all untraveled neighbors of maze.base.
		Set<Direction> untraveled = new HashSet<Direction>(maze.base.openDirections);
		untraveled.removeAll(maze.base.neighbors.keySet());

		// First ensure that all neighbors of base have been traveled. Once
		// this has been accomplished, change base.
		if (untraveled.isEmpty()) {
			// This block would be complete but maze.pathBetween() is not implemented.

			// Find the node in the open set that has the lowest overall score.
			Node next = maze.open.last();
			// Instruct bot to go there.
			bot.follow(maze.pathBetween(maze.current, next));
		} else {
			// This block is complete.
			
			// Pick a random unvisited neighbor of maze.base
			Node next = maze.base.neighbors.get(untraveled.iterator().next());

			// Construct a Path from current to base and then onwards to next.
			Path toNext = new Path();
			toNext.addStep(maze.current.getVectorTo(maze.base));
			toNext.addStep(maze.base.getVectorTo(next));

			// Instruct to bot to follow this path.
			bot.follow(toNext);
		}
	}

	/**
	 * Start stuff
	 */
	public void start() {
		bot.addNodeFoundListener(this);
		bot.go();
		return;
	}
}
